// Copyright 2018 The Grafeas Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package vulnerability implements functions to validate that the fields of vulnerability entities
// being passed into the API meet our requirements.
package vulnerability

import (
	"errors"
	"fmt"

	pkg "github.com/grafeas/grafeas/go/v1/api/validators/package"
	gpb "github.com/grafeas/grafeas/proto/v1/grafeas_go_proto"
)

// ValidateNote validates that a vulnerability note has all its required fields filled in.
func ValidateNote(v *gpb.VulnerabilityNote) []error {
	errs := []error{}

	if details := v.GetDetails(); details == nil {
		errs = append(errs, errors.New("details is required"))
	} else if len(details) == 0 {
		errs = append(errs, errors.New("details requires at least 1 element"))
	} else {
		for i, detail := range details {
			if detail == nil {
				errs = append(errs, fmt.Errorf("details[%d] detail cannot be null", i))
			} else {
				for _, err := range validateVulnerabilityDetail(detail) {
					errs = append(errs, fmt.Errorf("details[%d].%s", i, err))
				}
			}
		}
	}

	return errs
}

func validateVulnerabilityDetail(vd *gpb.VulnerabilityNote_Detail) []error {
	errs := []error{}

	if vd.GetAffectedCpeUri() == "" {
		errs = append(errs, errors.New("affected_cpe_uri is required"))
	}
	if vd.GetAffectedPackage() == "" {
		errs = append(errs, errors.New("affected_package is required"))
	}
	if ver := vd.GetAffectedVersionStart(); ver != nil {
		for _, err := range pkg.ValidateVersion(ver) {
			errs = append(errs, fmt.Errorf("affected_version_start.%s", err))
		}
	}
	if ver := vd.GetAffectedVersionEnd(); ver != nil {
		for _, err := range pkg.ValidateVersion(ver) {
			errs = append(errs, fmt.Errorf("affected_version_end.%s", err))
		}
	}
	if ver := vd.GetFixedVersion(); ver != nil {
		for _, err := range pkg.ValidateVersion(ver) {
			errs = append(errs, fmt.Errorf("fixed_version.%s", err))
		}
		if ver.Kind == gpb.Version_NORMAL {
			if vd.GetFixedCpeUri() == "" {
				errs = append(errs, errors.New("fixed_cpe_uri is required when fixed_version.kind is NORMAL"))
			}
			if vd.GetFixedPackage() == "" {
				errs = append(errs, errors.New("fixed_package is required when fixed_version.kind is NORMAL"))
			}
		}
	}

	return errs
}

// ValidateOccurrence validates that a vulnerability occurrence has all its required fields filled
// in.
func ValidateOccurrence(d *gpb.VulnerabilityOccurrence) []error {
	errs := []error{}

	if pkgIssue := d.GetPackageIssue(); pkgIssue == nil {
		errs = append(errs, errors.New("package_issue is required"))
	} else if len(pkgIssue) == 0 {
		errs = append(errs, errors.New("package_issue requires at least 1 element"))
	} else {
		for i, p := range pkgIssue {
			if p == nil {
				errs = append(errs, fmt.Errorf("package_issue[%d] package issue cannot be null", i))
			} else {
				for _, err := range validatePackageIssue(p) {
					errs = append(errs, fmt.Errorf("package_issue[%d].%s", i, err))
				}
			}
		}
	}

	return errs
}

func validatePackageIssue(p *gpb.VulnerabilityOccurrence_PackageIssue) []error {
	errs := []error{}

	if p.GetAffectedCpeUri() == "" {
		errs = append(errs, errors.New("affected_cpe_uri is required"))
	}
	if p.GetAffectedPackage() == "" {
		errs = append(errs, errors.New("affected_package is required"))
	}
	if ver := p.GetAffectedVersion(); ver == nil {
		errs = append(errs, errors.New("affected_version is required"))
	} else {
		for _, err := range pkg.ValidateVersion(ver) {
			errs = append(errs, fmt.Errorf("affected_version.%s", err))
		}
	}

	if ver := p.GetFixedVersion(); ver == nil {
		errs = append(errs, errors.New("fixed_version is required"))
	} else {
		for _, err := range pkg.ValidateVersion(ver) {
			errs = append(errs, fmt.Errorf("fixed_version.%s", err))
		}
		if ver.Kind == gpb.Version_NORMAL {
			if p.GetFixedCpeUri() == "" {
				errs = append(errs, errors.New("fixed_cpe_uri is required when fixed_version.kind is NORMAL"))
			}
			if p.GetFixedPackage() == "" {
				errs = append(errs, errors.New("fixed_package is required when fixed_version.kind is NORMAL"))
			}
		}
	}
	return errs
}
